package com.gitee.secretopen.plugin;



import cn.hutool.core.util.StrUtil;
import cn.hutool.setting.dialect.Props;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;


/**
 * 　@description: 读取目录中的插件
 * 　@author secret
 * 　@date 2021/1/28 13:13
 *
 */
public class SecretPluginClassLoader {

    /**
     * 插件目录
     */
    private File pluginPath;
    /**
     * 读取的插件列表
     */
    private List<Object> pluginList;

    private String pluginPackage="";//内置插件包

    private static final String FILE_TYPE = "file";

    private static final String JAR_TYPE = "jar";

    private static final String OUTSIDE_PLUGIN_IMPL_PACKAGE="secret-plugin.properties";//外部插件配置文件

//    private Logger logger = LogManager.getLogger(SecretPluginClassLoader.class.getName());;

    //只加载内置插件
    public SecretPluginClassLoader(String pluginPackage) {
        this.pluginPackage = pluginPackage;
        try {
            pluginList=loadPackagePlugin();
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    //只加载外部插件
    public SecretPluginClassLoader(File pluginPath) {
        this.pluginPath = pluginPath;
        try {
            pluginList=getPlugins();
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    //先加载内置插件，再加载外部插件
    public SecretPluginClassLoader(String pluginPackage,File pluginPath){
       this.pluginPackage=pluginPackage;
       this.pluginPath=pluginPath;
        try {
            pluginList=loadPackagePlugin();
            pluginList.addAll(getPlugins());
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    public static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 加载内置插件包中的插件
     */
    private List<Object> loadPackagePlugin(){
        List<Object> classSets = new ArrayList<>(); // 将类信息保存到集合中存储
        try {
            // 将包全名称替换为文件路径
            Enumeration<URL> urls = getClassLoader().getResources(pluginPackage.replace(".", "/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                // 协议名称，判断文件类型
                String protocol = url.getProtocol();
                // 多种文件类型判断，只需增加if即可
                if (FILE_TYPE.equals(protocol)) {
                    String basePath = url.getPath().replace("%20", " "); // 处理空格
                    addClass(classSets, basePath, pluginPackage); //获取类全路径，并反射加载类
                } else if (JAR_TYPE.equals(protocol)) {//如果是jar
                    JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
                    Enumeration<JarEntry> entries = jar.entries();
                    while (entries.hasMoreElements()) {
                        JarEntry entry = entries.nextElement();
                        if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
                            continue;
                        }
                        //去除.class得到class名
                        String className = entry.getName().substring(0, entry.getName().length() - 6);
                        className = className.replace('/', '.');
                        Class<?> clazz=null;
                        if(className.contains("module-info") || className.contains("META-INF")){//排除module-info.class和META-INF目录
                            continue;
                        }
//                        clazz = getClassLoader().loadClass(className);
                        if(className.contains(pluginPackage)){//只加载指定包名
                            clazz = getClassLoader().loadClass(className);
                        }
                        if (clazz!=null && clazz.getAnnotation(SecretPlugin.class) != null) {
                            classSets.add(clazz.getDeclaredConstructor().newInstance());
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException();
        }
        return classSets;
    }

    /**
     * 扫描文件目录，获取子目录的文件，并执行反射获取类文件信息
     * @param classSets
     * @param basePath
     * @param packageName
     */
    public static void addClass(List<Object> classSets, String basePath, String packageName) {
        // 获取子目录
        File[] files = new File(basePath).listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                // 过滤文件，必须为文件且以.class结尾，或者是文件目录
                return ((file.isFile() && file.getName().endsWith(".class")) || (file.isDirectory()));
            }
        });
        // 遍历获取文件信息
        for (File file : files) {
            String fileName = file.getName();
            // 如果为文件
            if (file.isFile()) {
                String className = fileName.substring(0, fileName.lastIndexOf("."));
                if (!StrUtil.isEmpty(className)) {
                    className = packageName + "." + className;
                }
                doAddClass(classSets, className);
            } else {
                // 获取文件名称，该文件为目录
                String subPath = fileName;
                if (!StrUtil.isEmpty(subPath)) {
                    subPath = packageName + "/" + subPath;
                }
                // 获取包名
                String subPackage = packageName;
                if (!StrUtil.isEmpty(subPackage)) {
                    subPackage = packageName + "." + fileName;
                }
                // 递归遍历文件信息
                addClass(classSets, subPath, subPackage);
            }
        }
    }

    /**
     * 将反射对象添加到集合中
     * @param classSets
     * @param className
     */
    private static void doAddClass(List<Object> classSets, String className) {
        Class<?> cls = loadClass(className, false);
        try {
            if (cls!=null && cls.getAnnotation(SecretPlugin.class) != null) {
                classSets.add(cls.getDeclaredConstructor().newInstance());
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    /**
     * 加载类
     * @param className
     * @param isInitialized
     * @return
     */
    public static Class<?> loadClass(String className, boolean isInitialized) {
        Class<?> cls = null;
        try {
            cls = Class.forName(className, isInitialized, getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException();
        }
        return cls;
    }

    /**
     * @description: 读取插件
     * @param
     * @return java.util.ArrayList<java.lang.Object>
     * @throws
     * @author secret
     * @date 2021/1/28 secret
     */
    private ArrayList<Object> getPlugins() throws Exception {

        if (!pluginPath.exists()) {
            throw new IOException("pathDoesNotExist");
        }

        if (!pluginPath.isDirectory()) {
            throw new IOException("thePathIsNotADirectory");
        }

        ArrayList<File> files = new ArrayList<File>();
        ArrayList<Object> plugins = new ArrayList<Object>();

        for (File file : pluginPath.listFiles()) {
            if (file.getName().endsWith(".jar")) {
                files.add(file);
            }
        }

        for (File f : files) {
            //获取外部插件的内置配置文件
            String pluginPackage=getPluginOutsidePackage(f);
            if(StrUtil.isEmpty(pluginPackage)){
                throw new IOException("Not Found outside-package Config");
            }
            JarFile jar = new JarFile(f);
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
                    continue;
                }
                String className = entry.getName().substring(0, entry.getName().length() - 6);
                className = className.replace('/', '.');
                if(className.contains("module-info")){//排除module-info.class
                    continue;
                }
                Class<?> clazz=null;
                if(className.contains(pluginPackage)) {
                    clazz = new URLClassLoader(new URL[]{f.toURL()}, Thread.currentThread().getContextClassLoader()).loadClass(className);
                }
                if (clazz!=null && clazz.getAnnotation(SecretPlugin.class) != null) {
                    plugins.add(clazz.getDeclaredConstructor().newInstance());
                }
            }
        }
        return plugins;
    }

    /**
     * 获取外部插件接口实现类所在包
     * @return
     */
    private String getPluginOutsidePackage(File f) throws Exception{
        String pluginPackage = null;
        //扫描jar包
        JarFile jar = new JarFile(f);
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            if (entry.getName().equals(OUTSIDE_PLUGIN_IMPL_PACKAGE)) {//扫描到了框架配置文件
                ClassLoader classLoader = new URLClassLoader(new URL[]{f.toURL()});
                URL url = classLoader.getResource(entry.getName());
                Props props = new Props(url, "utf-8");
                pluginPackage = props.get("secret.plugin.outside-package").toString();
            }
        }

        return pluginPackage;
    }

    /**
     * @description: 获取插件列表
     * @param
     * @return java.util.ArrayList<java.lang.Object>
     * @throws
     * @author secret
     * @date 2021/1/28 secret
     */
    public List<Object> getPluginList() {
        return pluginList;
    }

    /**
     * @description: 重新读取插件目录
     * @param
     * @return java.util.ArrayList<java.lang.Object>
     * @throws
     * @author secret
     * @date 2021/1/28 secret
     */
    public List<Object> reloadPluginList(){
        try {
            if(StrUtil.isNotEmpty(this.pluginPackage) && this.pluginPath!=null){//内置和外部插件同时加载
                pluginList=loadPackagePlugin();
                pluginList.addAll(getPlugins());
            } else if(StrUtil.isNotEmpty(this.pluginPackage)){//只加载内置插件
                pluginList=loadPackagePlugin();
            } else if(this.pluginPath!=null){//只加载外部插件
                pluginList=getPlugins();
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return pluginList;
    }
}
